#include <msp430x16x.h>
#include "sd.h"

/* useful defines to make code more meaningful */
#define R1 1
#define R1B 2
#define R2 3
#define R3 4
/* card reset */
#define CMD0 0
#define CMD0_R R1
/* set block length */
#define CMD16 16
#define CMD16_R R1
/* read block */
#define CMD17 17
#define CMD17_R R1
/* write block */
#define CMD24 24
#define CMD24_R R1
/* Get card OCR (sd mode) */
#define ACMD41 41
#define ACMD41_R R1
/* start app specific */
#define CMD55 55
#define CMD55_R R1
/* Read the OCR (spi mode) */
#define CMD58 58
#define CMD58_R R3
/* macros */
#define SPIASSERT P3OUT &= ~0x1 /* macro'd due to being a single command rather than a function call */
#define SPIDEASSERT P3OUT |= 0x1 /* macro'd due to being a single command rather than a function call */
#define SDPACKARG(a,v) a[3] = (unsigned char)(v >> 24); a[2] = (unsigned char)(v >> 16);\
                       a[1] = (unsigned char)(v >> 8);a[0] = (unsigned char)(v) /* macro'd as only called a few times */

/* internal functions */
static void SPISendByte(unsigned char c);
static unsigned char SPIReadByte(void);
static int sd_send_command(unsigned char cmd, unsigned char response_type);
static void sd_delay(char number);
static int sd_set_blocklen(unsigned long int length);
unsigned char sd_response[5];
unsigned char sd_argument[4];


static void SPISendByte(unsigned char c) {
  IFG1 &= ~URXIFG0;
  TXBUF0=c;
  while ((IFG1 & URXIFG0) == 0) { }
}

static unsigned char SPIReadByte(void) {
  unsigned char tmp;
  IFG1 &= ~URXIFG0;
  TXBUF0 = 0xff; /* need to push a byte out to recieve one back in */
  while ((IFG1 & URXIFG0) == 0) { }
  tmp = U0RXBUF;
  return (tmp);
}

int sdInit(void) {
  char i;

  /* enable power */
  P4SEL|=0x20; 
  P4DIR|=0x20; 
  P4OUT|=0x20;

  /************************************************************************************************
   * SPI / USART init
   ***********************************************************************************************/
  P3SEL |= 0xe; /* set the p3 pins we are using to spi */
  P3OUT |= 0x1; /* chip select high for inactive */
  P3DIR |= 0x1; /* set pin 0 as an output */
  U0CTL = (CHAR | SYNC | MM | SWRST); /* 8-bit spi with us as master */
  U0TCTL = (SSEL1 | STC | CKPH); /* normal 3 wire spi */
  U0BR0 = 0x14; /* start the clock at a low speed for mmc compatibility */
  U0BR1 = 0x0; /* must be zero */
  U0MCTL = 0x0; /* so must this one */
  ME1 |= USPIE0; /* enable the spi module */
  U0CTL &= ~SWRST; /* remove usart from software reset */

  /************************************************************************************************
   * SD init
   ***********************************************************************************************/
  /* delay for at least 74 clock cycles */
  SPIASSERT;
  sd_delay(100);
  SPIDEASSERT;
  sd_delay(2);
  /* set the card to idle */
  if (sd_send_command(CMD0, CMD0_R) == 0)
    return 0;

  /* Now wait until the card goes idle. Retry at most SD_IDLE_WAIT_MAX (100) times */
  i = 0;
  do {
    i++;
    /* Flag the next command as an application-specific command */
    if (sd_send_command(CMD55, CMD55_R) == 1) {
      /* Tell the card to send its OCR */
      sd_send_command(ACMD41, ACMD41_R);
    } else {
      /* No response, bail early */
      i = 100;
    }
  } while ((sd_response[0] & 0x1) == 0x1 && i < 100);

  /* As long as we didn't hit the timeout, assume we're OK. */
  if (i >= 100 || sd_send_command(CMD58, CMD58_R) == 0)
    return 0;

  /* At a very minimum, we must allow 3.3V. */
  if ((sd_response[2] & 0xc0) != 0xc0)
    return 0;

  /* Set the block length */
  if (sd_set_blocklen (512) != 1)
    return 0;
  /* If we got this far, initialization was okay. */
  /* we can also increase the clock speed of the usart back to something more useful */
  U0CTL |= SWRST;
  U0BR0 = 2;
  U0CTL &= ~SWRST;
  return 1;
}

static int sd_send_command(unsigned char cmd, unsigned char response_type) {
  int i;
  char response_length;
  unsigned char tmp;
  SPIASSERT;
  /* All data is sent MSB first, and MSb first */
  /* Send the header/command */
  /* Format:
  cmd[7:6] : 01
  cmd[5:0] : command */
  SPISendByte((cmd & 0x3F) | 0x40);
  for (i=3; i>=0; i--) {
    SPISendByte(sd_argument[i]);
  }

  /* This is the CRC. It only matters what we put here for the first
  command. Otherwise, the CRC is ignored for SPI mode unless we
  enable CRC checking. */
  SPISendByte(0x95);
  response_length = 0;
  switch (response_type) {
    case R1:
    case R1B:
      response_length = 1;
      break;
    case R2:
      response_length = 2;
      break;
    case R3:
      response_length = 5;
      break;
    default:
      break;
  }

  /* Wait for a response. A response can be recognized by the
  start bit (a zero) */
  i=0;
  do {
    tmp = SPIReadByte();
    i++;
  } while (((tmp & 0x80) != 0) && i < 100);

  /* Just bail if we never got a response */
  if (i >= 100) {
    SPIDEASSERT;
    return 0;
  }

  for (i=response_length-1; i>=0; i--) {
    sd_response[i] = tmp;
    /* This handles the trailing-byte requirement. */
    tmp = SPIReadByte();
  }


  /* If the response is a "busy" type (R1B), then there's some
  * special handling that needs to be done. The card will
  * output a continuous stream of zeros, so the end of the BUSY
  * state is signaled by any nonzero response. The bus idles
  * high.
  */
  i=0;
  if (response_type == R1B) {
    do {
      i++;
      tmp = SPIReadByte();
    } while (tmp != 0xFF);
    /* This should never time out, unless SDI is grounded.
    * Don't bother forcing a timeout condition here. */

    SPISendByte(0xFF);
  }
  SPIDEASSERT;
  return 1;
}

/* Sending idle bytes does do something other than suck up clock cycles, the sd card requires, in certain situations, a number of clock edges
   before it can correctly function - for example during initialisation on line 88 (where 74 clock edges are required) and when the sd card is
   disabled on line 223, where enough clock edges must be given so that the sd card can finish its operation and release the spi lines (if the
   line was not released (i.e. the sd card doesn't change to high impedance mode) then any other use of these lines would not work */
static void sd_delay(char number) {
  register char i;

  for (i=0; i<number; i++)
    SPISendByte(0xFF);
}

void sd_wait_notbusy(void) {
  while ((DMA0CTL & DMAEN) != 0);

  /* Reset the DMA controller */
  DMACTL0 = 0;

  /* Ignore the checksum */
  sd_delay(4);
  while (SPIReadByte() != 0xFF);

  /* Deassert CS */
  SPIDEASSERT;

  /* Send some extra clocks so the card can resynchronize on the next
  transfer */
  sd_delay(2);
}

/* Set the block length for all future block transactions */
/* Returns 1 if the function was successful */
static int sd_set_blocklen(unsigned long int length) {
  SDPACKARG(sd_argument, length);
  return (sd_send_command(CMD16, CMD16_R));
}

int sd_read_block(unsigned long int blockaddr, unsigned char *data) {
  unsigned long int i = 0;
  unsigned char tmp;

  const unsigned char blank = 0xFF;

  blockaddr <<= 9;
  sd_wait_notbusy();
  SDPACKARG(sd_argument, blockaddr);

  /* Need to add size checking */
  if (sd_send_command(CMD17, CMD17_R) == 0)
    return 0;
  /* Check for an error, like a misaligned read */
  if (sd_response[0] != 0)
    return 0;
  /* Re-assert CS to continue the transfer */
  SPIASSERT;
  /* Wait for the token */
  i=0;
  do {
    tmp = SPIReadByte();
    i++;
  } while ((tmp == 0xFF) && i < 400000 );

  if ((tmp & 0xe0) == 0) {
    /* Clock out a byte before returning */
    SPISendByte(0xFF);
    /* The card returned an error response. Bail and return 0 */
    return 0;
  }

  /* Prime the interrupt flags so things happen in the correct order. */
  IFG1 &= ~URXIFG0;
  IFG1 &= ~UTXIFG0;
  /* Get the block */
  /* Source DMA address: receive register. */
  DMA0SA = U0RXBUF_;

  /* Destination DMA address: the user data buffer. */
  DMA0DA = (unsigned short)data;

  /* The size of the block to be transferred */
  DMA0SZ = BLOCK_SIZE;
  /* Configure the DMA transfer*/
  DMA0CTL =
    DMADT_0 | /* Single transfer mode */
    DMASBDB | /* Byte mode */
    DMAEN | /* Enable DMA */
    DMADSTINCR1 | DMADSTINCR0; /* Increment the destination address */
  /* We depend on the DMA priorities here. Both triggers occur at
  the same time, since the source is identical. DMA0 is handled
  first, and retrieves the byte. DMA1 is triggered next, and
  sends the next byte. */
  /* Source DMA address: constant 0xFF (don't increment)*/
  DMA1SA = (unsigned short)&blank;
  /* Destination DMA address: the transmit buffer. */
  DMA1DA = U0TXBUF_;
  /* Increment the destination address */
  /* The size of the block to be transferred */
  DMA1SZ = BLOCK_SIZE-1;
  /* Configure the DMA transfer*/
  DMA1CTL =
    DMADT_0 | /* Single transfer mode */
    DMASBDB | /* Byte mode */
    DMAEN; /* Enable DMA */
  /* DMA trigger is UART receive for both DMA0 and DMA1 */
  DMACTL0 = DMA0TSEL_3 | DMA1TSEL_3;
  /* Kick off the transfer by sending the first byte */
  U0TXBUF = 0xFF;
  return 1;
}

int sd_write_block(unsigned long int blockaddr,unsigned char *data) {
  blockaddr <<= 9;
  sd_wait_notbusy();

  /* Pack the address */
  SDPACKARG(sd_argument, blockaddr);
  if (sd_send_command(CMD24, CMD24_R) == 0)
    return 0;
  /* Check for an error, like a misaligned write */
  if (sd_response[0] != 0)
    return 0;
  /* Re-assert CS to continue the transfer */
  SPIASSERT;
  /* The write command needs an additional 8 clock cycles before
  * the block write is started. */
  SPIReadByte();
  /* Clear any pending flags */
  IFG1 &= ~(URXIFG0 | UTXIFG0);
  /* Get the block */
  /* Source DMA address: the data buffer. */
  DMA0SA = (unsigned short)data;

  /* Destination DMA address: the UART send register. */
  DMA0DA = U0TXBUF_;
  /* The size of the block to be transferred */
  DMA0SZ = BLOCK_SIZE;
  /* Configure the DMA transfer*/
  DMA0CTL =
    DMADT_0 | /* Single transfer mode */
    DMASBDB | /* Byte mode */
    DMAEN | /* Enable DMA */
    DMASRCINCR1 | DMASRCINCR0; /* Increment the source address */
  /* DMA trigger is UART send */
  DMACTL0 = DMA0TSEL_3;
  /* Kick off the transfer by sending the first byte, the "start block"
  * token */
  U0TXBUF = 0xfe;
  return 1;
}

/* interface */
/* sdEnable and sdDisable, quicker to use than sdInit, providing sdInit has already been run */
void sdDisable(void) {
  /* check that the dma is not using the card */
  sd_wait_notbusy();
  /* disable sd card */
  SPIDEASSERT;
  /* return to orginal usart0 values */
  ME1 &= ~USPIE0; /* disable spi */
  U0CTL = SWRST; /* place usart in to software reset */
  U0TCTL = TXEPT; /* reset transmit register */
  U0BR0 = 0;
  U0BR1 = 0;
  U0MCTL = 0;
  /* disable pins that we used */
  P3SEL &= ~0xe;
  P3OUT &= ~0x1;
  P3DIR &= ~0x1;
}

void sdEnable(void) {
  /* re-enable pins that we use */
  P3SEL |= 0xe;
  P3OUT |= 0x1;
  P3DIR |= 0x1;
  /* set up usart0 */
  U0CTL = (CHAR | SYNC | MM | SWRST);
  U0TCTL = (SSEL1 | STC | CKPH);
  U0BR0 = 0x2;
  U0BR1 = 0x0;
  U0MCTL = 0x0;
  ME1 |= USPIE0; /* enable the spi module */
  U0CTL &= ~SWRST; /* remove usart from software reset */
  /* enable the sd card */
  SPIASSERT;
}
